import { httpError, Inject, Provide } from '@midwayjs/core'
import { Context } from '@midwayjs/koa'
import { InjectEntityModel } from '@midwayjs/typeorm'
import { Between, FindOptionsWhere, In, Like, Not, Repository } from 'typeorm'

import { APP_MESSAGES } from '@/constant'
import { DeleteDTO } from '@/dto'
import { SysPostEntity, SysRoleEntity, SysUserEntity } from '@/entity'
import { StateService } from '@/service'
import { PasswordUtil } from '@/util'

import {
  CreateUserDTO,
  FindUserPageDTO,
  ResetPasswordDTO,
  UpdateUserDTO
} from './user.dto'
import { UserTemplateSerialize } from './user.serialize'

@Provide()
export class UserService {
  @Inject()
  ctx: Context

  @InjectEntityModel(SysPostEntity)
  postModel: Repository<SysPostEntity>

  @InjectEntityModel(SysRoleEntity)
  roleModel: Repository<SysRoleEntity>

  @InjectEntityModel(SysUserEntity)
  userModel: Repository<SysUserEntity>

  async handleFindPage(dto: FindUserPageDTO) {
    const { pageNum, pageSize, beginDate, endDate, deptId, ...where } = dto

    const conditions = {
      delFlag: '0',
      createTime: beginDate ? Between(beginDate, endDate) : null,
      ...where
    } as FindOptionsWhere<SysUserEntity>

    const [list, total] = await this.userModel.findAndCount({
      relations: { dept: true },
      where:
        deptId && deptId !== '100'
          ? [
              {
                ...conditions,
                dept: {
                  deptId
                }
              },
              {
                ...conditions,
                dept: {
                  ancestors: Like(`%,${deptId}%`)
                }
              }
            ]
          : {
              ...conditions
            },
      take: pageSize,
      skip: (pageNum - 1) * pageSize
    })

    return {
      pageNum,
      pageSize,
      total,
      list
    }
  }

  async handleCreate(dto: CreateUserDTO) {
    const stateService = await this.ctx.requestContext.getAsync(StateService)

    const jwtPayload = stateService.getJwtPayload()

    const { userName, password, postIds, roleIds, ...rest } = dto

    const oldUser = await this.userModel.findOne({ where: { userName } })

    if (oldUser) {
      this.ctx.throw(new httpError.BadRequestError(APP_MESSAGES.USER_EXIST))
    }

    const newUser = new SysUserEntity()

    Object.assign<any, any, Partial<SysUserEntity>>(newUser, rest, {
      userName,
      password: PasswordUtil.crypto(password),
      createBy: jwtPayload.userName
    })

    if (postIds && postIds.length > 0) {
      newUser.posts = await this.postModel.find({
        where: { postId: In(postIds) }
      })
    }

    if (roleIds && roleIds.length > 0) {
      newUser.roles = await this.roleModel.find({
        where: { roleId: In(roleIds) }
      })
    }

    await this.userModel.save(newUser)
  }

  async handleUpdate(id: string, dto: UpdateUserDTO) {
    const stateService = await this.ctx.requestContext.getAsync(StateService)

    const jwtPayload = stateService.getJwtPayload()

    const { postIds, roleIds, ...rest } = dto

    const user = await this.userModel.findOne({
      relations: { posts: true, roles: true },
      where: { userId: id }
    })

    if (!user) {
      this.ctx.throw(new httpError.BadRequestError(APP_MESSAGES.USER_NOT_EXIST))
    }

    Object.assign<any, any, Partial<SysUserEntity>>(user, rest, {
      updateBy: jwtPayload.userName
    })

    if (postIds && postIds.length) {
      user.posts = await this.postModel.find({ where: { postId: In(postIds) } })
    }

    if (roleIds && roleIds.length) {
      user.roles = await this.roleModel.find({ where: { roleId: In(roleIds) } })
    }

    await this.userModel.save(user)
  }

  async handleGetFormData(id?: string) {
    const posts = await this.postModel.find({ where: { status: '0' } })
    const roles = await this.roleModel.find({
      where: { delFlag: '0', roleId: Not('1') }
    })

    const data = {
      posts,
      roles
    } as {
      posts: SysPostEntity[]
      roles: SysRoleEntity[]
      postIds?: string[]
      roleIds?: string[]
    }

    if (id) {
      const user = await this.userModel.findOne({
        relations: { posts: true, roles: true },
        where: { userId: id }
      })

      data.postIds = user.posts.map(v => v.postId)
      data.roleIds = user.roles.map(v => v.roleId)
    }

    return data
  }

  async handleDelete(dto: DeleteDTO) {
    const { ids } = dto

    const list = await this.userModel.find({
      where: { userId: In(ids) }
    })

    for (const user of list) {
      user.delFlag = '2'
      user.posts = []
      user.roles = []

      await this.userModel.save(user)
    }
  }

  async handleResetPassword(id: string, dto: ResetPasswordDTO) {
    const stateService = await this.ctx.requestContext.getAsync(StateService)

    const jwtPayload = stateService.getJwtPayload()

    const user = await this.userModel.findOne({ where: { userId: id } })

    if (!user) {
      this.ctx.throw(new httpError.BadRequestError(APP_MESSAGES.USER_NOT_EXIST))
    }

    const { password } = dto

    Object.assign<any, Partial<SysUserEntity>>(user, {
      password: PasswordUtil.crypto(password),
      updateBy: jwtPayload.userName
    })

    await this.userModel.save(user)
  }

  async handleToggleStatus(id: string) {
    const user = await this.userModel.findOne({ where: { userId: id } })

    if (!user) {
      this.ctx.throw(new httpError.BadRequestError(APP_MESSAGES.USER_NOT_EXIST))
    }

    user.status = user.status === '0' ? '1' : '0'

    await this.userModel.save(user)
  }

  async handleImport(data: UserTemplateSerialize[]) {
    let success = 0
    let fail = 0

    for (const item of data) {
      const isExist = await this.#checkIsExist(item.userName)

      if (isExist) {
        fail++
      } else {
        await this.handleCreate({ roleIds: [], postIds: [], ...item })
        success++
      }
    }

    return {
      success,
      fail
    }
  }

  async #checkIsExist(userName: string) {
    return await this.userModel.findOne({ where: { userName } })
  }
}
